package com.github.datalineage.graphdb;

import com.github.datalineage.data.Node;
import com.github.datalineage.data.Relation;
import com.github.datalineage.data.Tuple;
import org.apache.commons.lang.StringUtils;
import org.neo4j.driver.*;
import org.neo4j.driver.Record;
import org.neo4j.driver.internal.value.NodeValue;
import org.neo4j.driver.internal.value.RelationshipValue;
import org.neo4j.driver.types.Relationship;
import org.springframework.stereotype.Component;
import org.ssssssss.magicapi.config.MagicModule;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Component
public class Neo4jAdapter implements GraphAdapter, MagicModule {
    private Driver driver;
    private Session session;
    public Neo4jAdapter() {
    }

    @Override
    public void open(String url, String user, String password) {
        driver = GraphDatabase.driver( url, AuthTokens.basic( user, password ) );
        session = driver.session();
    }
//
//    @Override
//    public void open() {
//        driver = GraphDatabase.driver( "bolt://36.134.87.10:18289", AuthTokens.basic( "neo4j", "password" ) );
//        session = driver.session();
//    }

    private boolean isExist(Transaction tx, Node node) {
        String clause=String.format("MATCH (a:%s {code: \"%s\"}) return a", node.getType(), node.getCode());
        Result result = tx.run(clause);
        boolean hasResult=result.hasNext();
        return hasResult;
    }

    private boolean isExistLink(Transaction tx, Node node1, Node node2, Relation relation) {
        String clause=String.format("MATCH (a:%s {code: \"%s\"})-[r:%s]->(b:%s {code: \"%s\"}) return r", node1.getType(), node1.getCode(), relation.getCode(), node2.getType(), node2.getCode());
        Result result = tx.run(clause);
        boolean hasResult=result.hasNext();
        return hasResult;
    }

    private Node convert(Value value) {
        Node nodeRet=null;
        if(value instanceof NodeValue) {
            nodeRet=new Node();
            org.neo4j.driver.types.Node node = value.asNode();
            String curNodeType = node.labels().iterator().next();
            nodeRet.setType(curNodeType);
            Map<String, Object> attrs = node.asMap();
            System.out.println(attrs);
            nodeRet.getAttributes().putAll(attrs);
        }
        return nodeRet;
    }

    private Relation convertRelation(Value value) {
        Relation relationRet=null;
        if(value instanceof RelationshipValue) {
            relationRet=new Relation();
            Relationship relationship = value.asRelationship();
            String type=relationship.type();
            relationRet.setType(type);
            relationRet.setCode(type);
            relationRet.setName(relationship.get("name").asString());
        }

        return relationRet;
    }

    @Override
    public List<Node> searchEffect(String nodeType, String nodeCode) {
        List<Node> results=new ArrayList<>();

        session.readTransaction(new TransactionWork<String>() {
            @Override
            public String execute( Transaction tx ) {
                String clause = String.format("MATCH (a)-[*1..3]->(b) where (a:%s and a.code='%s') or (b:%s and b.code='%s') return a,b", nodeType, nodeCode, nodeType, nodeCode);

                Result result = tx.run(clause);

                if(result.hasNext()) {
                    List<Record> records = result.list();
                    for(Record record:records) {
                        System.out.println(record);
                        List<Value> values=record.values();

                        for(Value value:values) {
                            String type=value.asNode().labels().iterator().next();
                            Node node=new Node();
                            node.setType(type);
                            node.getAttributes().putAll(value.asMap());
                            results.add(node);
                        }
                    }
                }

                return "success";
            }

        });

        return results;
    }

    @Override
    public List<Tuple> searchLink(String nodeType, String relationCode, String nodeCode) {
        List<Tuple> results=new ArrayList<>();

        session.readTransaction(new TransactionWork<String>() {
            @Override
            public String execute( Transaction tx ) {
                String clause = null;
                if(StringUtils.isNotBlank(relationCode)) {
                    clause = String.format("MATCH (a)-[r:%s]->(b) where (a:%s and a.code='%s') or (b:%s and b.code='%s') return a,b,r", relationCode, nodeType, nodeCode, nodeType, nodeCode);
                } else {
                    clause = String.format("MATCH (a)-[r]->(b) where (a:%s and a.code='%s') or (b:%s and b.code='%s') return a,b,r", nodeType, nodeCode, nodeType, nodeCode);
                }
                Result result = tx.run(clause);

                if(result.hasNext()) {
                    List<Record> records = result.list();
                    for(Record record:records) {
                        System.out.println(record);
                        List<Value> values=record.values();

                        Node node1=convert(values.get(0));
                        Node node2=convert(values.get(1));

                        Relation relation=convertRelation(values.get(2));


                        Tuple tuple=new Tuple();
                        tuple.setNode1(node1);
                        tuple.setNode2(node2);
                        tuple.setRelation(relation);
                        results.add(tuple);
                    }
                }

                return "success";
            }

        });

        return results;
    }

    @Override
    public void creatLink(Node node1, Node node2, Relation relation) {
        try
        {
            String greeting = session.writeTransaction( new TransactionWork<String>() {
                @Override
                public String execute( Transaction tx )
                {
                    if(isExist(tx, node1) && isExist(tx, node2)) {
                        //如果node1和node2都存在

                        if(!isExistLink(tx, node1, node2, relation)) {
                            String clause = String.format("MATCH (a:%s {code: '%s'})  MATCH (b:%s {code: '%s'})  CREATE (a)-[:%s {name: '%s'}]->(b)", node1.getType(), node1.getCode(), node2.getType(), node2.getCode(), relation.getCode(), relation.getName());
                            Result result = tx.run(clause);
                            return "success";
                        } else {
                            return "success";
                        }

                    } else if(isExist(tx, node1)) {
                        //如果node1存在, node2不存在
                        String clause = String.format("MATCH (a:%s {code: '%s'})  CREATE (b:%s %s)  CREATE (a)-[:%s {name: '%s'}]->(b)", node1.getType(), node1.getCode(), node2.getType(), node2.getAttrJSON(), relation.getCode(), relation.getName());
                        Result result = tx.run(clause);
                        return "success";

                    } else if(isExist(tx, node2)) {
                        //如果node1不存在，node2存在
                        String clause = String.format("MATCH (b:%s {code: '%s'})  CREATE (a:%s %s)  CREATE (a)-[:%s {name: '%s'}]->(b)", node2.getType(), node2.getCode(), node1.getType(), node1.getAttrJSON(), relation.getCode(), relation.getName());
                        Result result = tx.run(clause);
                        return "success";

                    } else {
                        //如果node1和node2都不存在
                        String clause = String.format("CREATE (%s:%s %s), (%s:%s %s), (%s)-[:%s {name: '%s'}]->(%s)", node1.getCode(), node1.getType(), node1.getAttrJSON(), node2.getCode(), node2.getType(), node2.getAttrJSON(), node1.getCode(), relation.getCode(), relation.getName(), node2.getCode());
                        Result result = tx.run(clause);
                        return "success";
                    }
                }
            } );
            System.out.println( greeting );
        } catch (Exception e) {

        }
    }

    @Override
    public void close() {
        if(session!=null) {
            session.close();
        }
    }

    @Override
    public String getModuleName() {
        return "graphdbAdapter";
    }
}
